﻿using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.Identity;

/// <summary>
/// 登录现在由统一认证提供
/// 在还没有系统账户的情况下, 在认证完成后, 可以通过手机号码和短信验证码登录系统
/// </summary>
public class PhoneNumberCaptchaService(
    ILogger<PhoneNumberCaptchaService> logger,
    IHttpContextAccessor httpContextAccessor,
    IDataService<User> dataService,
    IDistributedCache cache,
    ISmsService smsService,
    ISmsCaptchaService captchaService,
    HttpSetting httpSetting,
    ISmsEndpoint endpoint)
{
    private readonly ILogger<PhoneNumberCaptchaService> _logger = logger;
    private readonly IDistributedCache _cache = cache;
    private readonly ISmsService _smsService = smsService;
    private readonly ISmsCaptchaService _captchaService = captchaService;
    private readonly IDataService<User> _dataService = dataService;
    private readonly ISmsEndpoint _endpoint = endpoint;
    private readonly HttpSetting _httpSetting = httpSetting;
    private readonly HttpContext _httpContext = httpContextAccessor.HttpContext!;
    private readonly DistributedCacheEntryOptions _captchaOptions = new() { AbsoluteExpiration = DateTime.Today.AddDays(UNIT_ONE).AddMinutes(endpoint.Expiration) };
    private static readonly string _cacheKeyPrefix = CACHE_USER + nameof(AuthType.Captcha) + CHAR_UNDERLINE;

    /// <summary>
    /// 发送短信验证码
    /// 5 分钟内不会发送重复短信,
    /// 5 分钟内未收到可再次发送, 此时请传递 force 参数, 值为 true
    /// <param name="phoneNumber">手机号码</param>
    /// <param name="isLogin">发送短信验证码是否用于登录</param>
    /// </summary>
    public async Task<CaptchaViewModel> SendCaptchaAsync(string phoneNumber, bool isLogin = false)
    {
        if (!phoneNumber.IsPhoneNumber())
        {
            throw new BadHttpRequestException("请输入正确的手机号码!");
        }

        bool force = _dataService.GetFromRequest<bool>(nameof(force));
        var state = _dataService.GetFromHttpContext<DataState>(CLAIM_TYPE_USER_STATE);
        var cacheKey = _cacheKeyPrefix + ((isLogin || state == DataState.Draft) ? phoneNumber : _dataService.GetUserId());
        var captcha = await _cache.GetValueAsync<CaptchaViewModel>(cacheKey) ?? new CaptchaViewModel { PhoneNumber = phoneNumber };

        //验证发送间隔
        if ((DateTime.UtcNow - captcha.SendTime.ToUniversalTime()).TotalSeconds <= _endpoint.SendInterval)
        {
            _logger.LogInformation($"用户手机号码 {captcha.PhoneNumber} 的短信验证码发送时间不足: {_endpoint.SendInterval} 秒, 不在发送新短信!");
            throw new BadHttpRequestException($"短信验证码发送时间不足 {_endpoint.SendInterval} 秒, 请稍后再试!");
        }

        SendValidate(captcha);
        if (captcha.ErrorMessage is not null)
        {
            throw new BadHttpRequestException(captcha.ErrorMessage);
        }

        if (captcha.PhoneNumber == phoneNumber && captcha.Expiration.ToUniversalTime() > DateTime.UtcNow && !force)
        {
            //已发送过验证码且过期时间未到, 不在继续发送, 直接返回
            _logger.LogInformation($"用户的手机号码 {phoneNumber} 已发送过验证码且未过期, 不再继续发送!");
            return captcha;
        }

        //发送短信验证码
        //手机短信验证码的缓存保存时间有效期为当天, 用以确认当天验证失败次数不能超限
        captcha.PhoneNumber = phoneNumber;
        captcha.Captcha = await _captchaService.SendCaptchaAsync(phoneNumber);
        captcha.SendTime = DateTime.UtcNow;
        captcha.Expiration = captcha.SendTime.AddMinutes(_endpoint.Expiration);
        captcha.SendCount++;
        await _cache.SetValueAsync(cacheKey, captcha, _captchaOptions);
        return captcha;
    }
    /// <summary>
    /// 使用特定模板发送短信验证码
    /// 5 分钟内不会发送重复短信,
    /// 5 分钟内未收到可再次发送, 此时请传递 force 参数, 值为 true
    /// <param name="phoneNumber">手机号码</param>
    /// <param name="smsModel">使用特定模板发送短信验证码</param>
    /// </summary>
    public async Task<CaptchaViewModel> SendCaptchaAsync(string phoneNumber, SmsModel smsModel)
    {
        if (!phoneNumber.IsPhoneNumber())
        {
            throw new BadHttpRequestException("请输入正确的手机号码!");
        }

        bool force = _dataService.GetFromRequest<bool>(nameof(force));
        var cacheKey = _cacheKeyPrefix + phoneNumber + CHAR_UNDERLINE + smsModel.TemplateCode;
        if (!string.IsNullOrWhiteSpace(smsModel.Purpose))
        {
            cacheKey += CHAR_UNDERLINE + smsModel.Purpose;
        }

        var captcha = await _cache.GetValueAsync<CaptchaViewModel>(cacheKey) ?? new CaptchaViewModel { PhoneNumber = phoneNumber };

        //验证发送间隔
        if ((DateTime.UtcNow - captcha.SendTime.ToUniversalTime()).TotalSeconds <= _endpoint.SendInterval)
        {
            _logger.LogInformation($"用户手机号码 {captcha.PhoneNumber} 的短信验证码发送时间不足: {_endpoint.SendInterval} 秒, 不在发送新短信!");
            throw new BadHttpRequestException($"短信验证码发送时间不足 {_endpoint.SendInterval} 秒, 请稍后再试!");
        }

        SendValidate(captcha);
        if (captcha.ErrorMessage is not null)
        {
            throw new BadHttpRequestException(captcha.ErrorMessage);
        }

        if (captcha.PhoneNumber == phoneNumber && captcha.Expiration.ToUniversalTime() > DateTime.UtcNow && !force)
        {
            //已发送过验证码且过期时间未到, 不在继续发送, 直接返回
            _logger.LogInformation($"用户的手机号码 {phoneNumber} 已发送过验证码且未过期, 不再继续发送!");
            return captcha;
        }

        //发送短信验证码
        var code = new Random(DateTime.UtcNow.Millisecond).Next(100000, 999999).ToString();

        _logger.LogDebug($"SMS service will send captcha: {code} to {phoneNumber}");
        smsModel.TemplateParam.Add(nameof(code), code);
        await _smsService.SendAsync(smsModel.SignName, smsModel.TemplateCode, smsModel.TemplateParam, phoneNumber);
        _logger.LogInformation($"SMS service send captcha: {code} to {phoneNumber} success");

        //手机短信验证码的缓存保存时间有效期为当天, 用以确认当天验证失败次数不能超限
        captcha.PhoneNumber = phoneNumber;
        captcha.Captcha = code;
        captcha.SendTime = DateTime.UtcNow;
        captcha.Expiration = captcha.SendTime.AddMinutes(_endpoint.Expiration);
        captcha.SendCount++;
        await _cache.SetValueAsync(cacheKey, captcha, _captchaOptions);
        return captcha;
    }

    /// <summary>
    /// 验证手机短信验证码
    /// 使用场景是在用户已登录时
    /// </summary>
    /// <param name="code">短信验证码</param>
    /// <returns></returns>
    public async Task<CaptchaViewModel> ValidateAsync(string code)
    {
        if (string.IsNullOrWhiteSpace(code))
        {
            _logger.LogInformation("必须输入验证码!");
            return new CaptchaViewModel { ErrorMessage = "必须输入验证码!" };
        }

        var user = await _dataService.GetAsync(_dataService.UserId);
        if (user is null)
        {
            return new CaptchaViewModel { ErrorMessage = "当前用户不存在!" };
        }

        var cacheKey = _cacheKeyPrefix + _dataService.UserId;
        var captcha = await _cache.GetValueAsync<CaptchaViewModel>(cacheKey);
        if (captcha is null)
        {
            _logger.LogInformation("短信验证码已过期, 请重新发送!");
            return new CaptchaViewModel { ErrorMessage = "短信验证码已过期, 请重新发送!" };
        }

        SendValidate(captcha);
        if (captcha.ErrorMessage is not null)
        {
            return captcha;
        }

        if (captcha.Captcha != code)
        {
            captcha.FailedCount++;
            await _cache.SetValueAsync(cacheKey, captcha, _captchaOptions);
            _logger.LogInformation($"用户手机号码 {captcha.PhoneNumber} 的短信验证码不正确! 已发送的验证码: {captcha.Captcha}, 收到的验证码: {code}, 短信验证码不正确!");
            captcha.ErrorMessage = $"短信验证码不正确, 请重新输入!";
            return captcha;
        }

        if (captcha.Expiration.ToUniversalTime() <= DateTime.UtcNow)
        {
            _logger.LogInformation($"用户手机号码 {captcha.PhoneNumber} 的短信验证码已过期!");
            captcha.ErrorMessage = $"短信验证码已过期, 请重新发送!";
            return captcha;
        }

        _logger.LogInformation($"用户手机号码 {captcha.PhoneNumber} 已发送的验证码: {captcha.Captcha}, 收到的验证码: {code}, 短信验证码验证通过!");
#if !DEBUG
        await _cache.RemoveAsync(cacheKey);
#endif

        if (!user.PhoneNumberConfirmed)
        {
            user.PhoneNumber = captcha.PhoneNumber;
            user.PhoneNumberConfirmed = true;
            user.SecurityStamp = KeyGenerator.GetStringKey();
            if (user.Type == AuthorizeType.ThirdParty && user.PasswordHash is null)
            {
                user.UserName = user.PhoneNumber;
            }

            await _dataService.UpdateAsync(user);
        }

        _logger.LogInformation($"已验证用户: {user.UserName} 的手机号码: {captcha.PhoneNumber}!");
        return captcha;
    }
    /// <summary>
    /// 验证手机短信验证码
    /// 使用场景是在用户未登录时
    /// </summary>
    /// <returns></returns>
    public async Task<CaptchaViewModel> ValidateAsync(string phoneNumber, string code, string? templateCode = default, string? purpose = default)
    {
        if (string.IsNullOrWhiteSpace(phoneNumber))
        {
            _logger.LogInformation("手机号码不能为空!");
            return new CaptchaViewModel { ErrorMessage = "手机号码不能为空!" };
        }

        if (string.IsNullOrWhiteSpace(code))
        {
            _logger.LogInformation("必须输入验证码!");
            return new CaptchaViewModel { ErrorMessage = "必须输入验证码!" };
        }

        var cacheKey = _cacheKeyPrefix + phoneNumber;
        if (!string.IsNullOrWhiteSpace(templateCode))
        {
            cacheKey += CHAR_UNDERLINE + templateCode;
        }

        if (!string.IsNullOrWhiteSpace(purpose))
        {
            cacheKey += CHAR_UNDERLINE + purpose;
        }

        var captcha = await _cache.GetValueAsync<CaptchaViewModel>(cacheKey);
        if (captcha is null)
        {
            _logger.LogInformation("短信验证码已过期, 请重新发送!");
            return new CaptchaViewModel { ErrorMessage = "短信验证码已过期, 请重新发送!" };
        }

        SendValidate(captcha);
        if (captcha.ErrorMessage is not null)
        {
            return captcha;
        }

        if (captcha.Captcha != code)
        {
            captcha.FailedCount++;
            await _cache.SetValueAsync(cacheKey, captcha, _captchaOptions);
            _logger.LogInformation($"用户手机号码 {phoneNumber} 的短信验证码不正确! 已发送的验证码: {captcha.Captcha}, 收到的验证码: {code}, 短信验证码不正确!");
            captcha.ErrorMessage = $"短信验证码不正确, 请重新输入!";
            return captcha;
        }

        if (captcha.Expiration.ToUniversalTime() <= DateTime.UtcNow)
        {
            _logger.LogInformation($"用户手机号码 {phoneNumber} 的短信验证码已过期!");
            captcha.ErrorMessage = $"短信验证码已过期, 请重新发送!";
            return captcha;
        }

        _logger.LogInformation($"用户手机号码 {phoneNumber} 已发送的验证码: {captcha.Captcha}, 收到的验证码: {code}, 短信验证码验证通过!");

        captcha.IsValid = true;
        await _cache.SetValueAsync(cacheKey, captcha);
        if (_httpSetting.SecurityLevel == SecurityLevel.Basic)
        {
            _httpContext.AddClaims(new Claim(CLAIM_TYPE_USER_IDENTIFIER, phoneNumber));
        }

        return captcha;
    }
    /// <summary>
    /// 验证手机号码
    /// </summary>
    /// <param name="code"></param>
    /// <param name="returnUrl"></param>
    /// <returns></returns>
    public async Task<PhoneNumberConfirmedViewModel> ConfirmAsync(string code, string? returnUrl = default)
    {
        var request = _httpContext.Request;
        string? phoneNumber = request.GetRequestOption<string>(nameof(phoneNumber));
        phoneNumber ??= request.Form[CLAIM_TYPE_PHONE_NUMBER].ToString();
        CaptchaViewModel? captcha = null;

        try
        {
            var state = _httpContext.GetFromHttpContext<DataState>(CLAIM_TYPE_USER_STATE);
            if (state == DataState.Draft)
            {
                if (string.IsNullOrWhiteSpace(phoneNumber))
                {
                    return new()
                    {
                        ReturnUrl = returnUrl,
                        ErrorMessage = "请输入手机号码!"
                    };
                }

                captcha = await ValidateAsync(phoneNumber: phoneNumber.ToString(), code);
            }
            else
            {
                captcha = await ValidateAsync(code);
            }

            returnUrl ??= request.GetRequestOption<string>(CLAIM_TYPE_REDIRECT_URL) ?? request.GetRequestOption<string>(nameof(returnUrl)) ?? _httpContext.GetFromHttpContext<string>(CLAIM_TYPE_REDIRECT_URL) ?? CHAR_SLASH.ToString();
            return new PhoneNumberConfirmedViewModel
            {
                PhoneNumber = captcha.PhoneNumber,
                SendTime = captcha.SendTime,
                ErrorMessage = captcha.ErrorMessage,
                FailedCount = captcha.FailedCount,
                ReturnUrl = returnUrl
            };
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, $"验证用户短信验证码失败, 错误详情: " + ex.GetMessage());
            return new PhoneNumberConfirmedViewModel
            {
                PhoneNumber = phoneNumber!,
                ReturnUrl = returnUrl,
                FailedCount = captcha?.FailedCount ?? 0,
                ErrorMessage = $"验证用户短信验证码失败!"
            };
        }
    }

    /// <summary>
    /// 验证次数
    /// </summary>
    /// <param name="captcha"></param>
    /// <returns></returns>
    /// <exception cref="BadHttpRequestException"></exception>
    private void SendValidate(CaptchaViewModel captcha)
    {
        //验证发送次数超限
        if (captcha.SendCount >= _endpoint.SendCount)
        {
            _logger.LogInformation($"用户手机号码 {captcha.PhoneNumber} 的短信验证码发送次数超出限制次数: {_endpoint.SendCount}, 当天已限制再次发送!");
            captcha.ErrorMessage = "短信验证码发送次数超出限制, 24小时内您不能再次发送短信验证码!";
        }

        //验证次数超限
        if (captcha.FailedCount >= _endpoint.RetryCount)
        {
            _logger.LogInformation($"用户手机号码 {captcha.PhoneNumber} 的短信验证码验证失败次数超出限制次数: {_endpoint.RetryCount}, 当天已限制再次短信验证!");
            captcha.ErrorMessage = "短信验证码验证失败次数超出限制, 24小时内您不能再次进行短信验证!";
        }
    }
}